﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using OpenLS.Drawing;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Formats.Biff.OfficeArt;
using OpenLS.Spreadsheet.Formats.Biff.Records;

namespace OpenLS.Spreadsheet.Formats.Biff
{
    class WorksheetSequencer: Sequencer
    {
        private readonly BiffWriterWorkbookContext _parentContext;
        BiffWriterWorkbookContext Context{get
        {
            Contract.Ensures(Contract.Result<BiffWriterWorkbookContext>() != null);
            return _parentContext;
        }}
        private readonly Worksheet _worksheet;
        Worksheet Worksheet { get {
            Contract.Ensures(Contract.Result<Worksheet>() != null);return  _worksheet; } }

        public WorksheetSequencer(BiffWriterWorkbookContext context, Worksheet sheet)
        {
            _parentContext = context;
            _worksheet = sheet;
        }

        public IEnumerable<object> GetWorksheetRecords()
        {
            yield return new BofRecord(_parentContext.SheetToBoundSheet[_worksheet]);
            yield return GetWorksheetContentRecords();
        }

        private  IEnumerable<object> GetWorksheetContentRecords()
        {
            //[Uncalced]
            List<RowGroup> rowGroups = new List<RowGroup>(GetRowGroups());
            yield return new IndexRecord(this.Worksheet, rowGroups);
            yield return GlobalsRecords();
            yield return PAGESETUP();
            //[HeaderFooter]
            if (this.Worksheet.Background != null)
                yield return Background();
            foreach (var p in Worksheet.CustomProperties)
                foreach (var r in BigNameRecord.GetBigNameRecords(p))
                    yield return r;
            yield return Protection();
            yield return Columns();
            //[SCENARIOS] 
            yield return SortAndFilter();
            yield return new DimensionsRecord(this.Worksheet);
            //\\  yield return new Window2Record(worksheet);
            yield return CellTable(rowGroups);
            yield return GetObjects();
            //*HFPicture 
            yield return Notes();
            //*PIVOTVIEW [DCON] 1*WINDOW
            yield return new Window2Record(this.Worksheet);


            var sel = this.Worksheet.Selection;
            if (sel == null)
                sel = this.Worksheet[0, 0].ToRange();
            if (this.Worksheet.BottomRightPane != null && this.Worksheet.View != null && this.Worksheet.View.Pane != null)
                yield return new PaneRecord(this.Worksheet);
            foreach (WorksheetPane pane in this.Worksheet.GetPanes())
                yield return new SelectionRecord(sel, pane);


            //*CUSTOMVIEW *2SORT [DxGCol]
            yield return MergedCells();
            // [LRng] *QUERYTABLE [PHONETICINFO] 
            yield return ConditionalFormatting_();
            //CONDFMTS 
            //*HLINK 
            yield return GetHLinks();
            //[DVAL]
            yield return DataValidation_();
            if (this.Worksheet.View != null && this.Worksheet.View.ZoomScale != 100)
                yield return new SCLRecord(this.Worksheet.View.ZoomScale);
            yield return new CodeNameRecord(this.Worksheet.Name);
            if (this.Worksheet.Protection != null)
                yield return new FeatHdrRecord(this.Worksheet.Protection);
            else
                yield return new FeatHdrRecord();
            //*WebPub *CellWatch [SheetExt] *FEAT 
            yield return GetFeat11();
//                *RECORD12 
            yield return new EofRecord();

        }

        private IEnumerable<BiffRecord> Protection()
        {
            if (Worksheet.Protection == null)
                yield break;
            if (Worksheet.Protection.Sheet)
                yield return new ProtectRecord();
            if (Worksheet.Protection.Scenarios)
                yield return new ScenarioProtectRecord(1);
            if (this.Worksheet.Protection.Objects)
                yield return new ObjProtectRecord();
            if (this.Worksheet.Protection.PasswordHash != null)
            {
                yield return new PasswordRecord(this.Worksheet.Protection.PasswordHash);
            }
        }

        private IEnumerable<BiffRecord> GetFeat11()
        {
            if (this.Worksheet.Tables.Count == 0)
                yield break;
            yield return new FeatHdr11Record(Enumerable.Max(this.Worksheet.Tables, t => t.Id + 1));
            int i = 1;
            foreach (var table in this.Worksheet.Tables)
            {
                yield return new Feature11Record(table, i);
                yield return new List12Record(new List12BlockLevel(table), i);
                yield return new List12Record(new List12TableStyleClientInfo(table), i);
                yield return new List12Record(new List12DisplayName(table), i);
                i++;
            }
        }

        private  IEnumerable<BiffRecord> Notes()
        {
//#if COMMENTS
            foreach (var cell in Enumerable.Where(this.Worksheet.GetCells(), c => c.Comment != null))
            {
                yield return new NoteRecord(cell, Context);
            }
        }

        private  IEnumerable<OfficeArtSpContainer> GetShapes(OfficeArtWriteContext context)
        {
            List<Cell> cellWithComments =
                Enumerable.ToList(Enumerable.Where(this.Worksheet.GetCells(), c => c.Comment != null));
            var pictureAnchors = Enumerable.ToList(Enumerable.Where(this.Worksheet.Anchors, a => a.Content is Picture || a.Content is XamlElement));
            if (cellWithComments.Count == 0 && pictureAnchors.Count == 0)
                yield break;
            uint shapeId = 1024;
            yield return OfficeArtSpContainer.CreateGroupContainer(shapeId++);
            foreach (Cell cell in cellWithComments)
            {
                yield return OfficeArtSpContainer.CreateFromComment(cell.Comment, shapeId++, context);
            }
            foreach (var picture in pictureAnchors)
            {
                yield return OfficeArtSpContainer.CreateFromPictureAnchor(picture, shapeId ++, context);
            }

        }
        public static IEnumerable<Picture>GetPictures (Worksheet worksheet, PictureContext pictureContext)
        {
            Contract.Assert(pictureContext != null);
            return
                Enumerable.OfType<Picture>(
                    Enumerable.Select(
                        Enumerable.Select(worksheet.Anchors, anchor => anchor.Content),
                        content =>  pictureContext.GetPicture(content)));
        }
        private  IEnumerable<BiffRecord> GetObjects()
        {
            List<Cell> cellWithComments =
                Enumerable.ToList(Enumerable.Where(this.Worksheet.GetCells(), c => c.Comment != null));
            var pictures = Enumerable.ToList(GetPictures(this.Worksheet, _parentContext.PictureContext));
            if (cellWithComments.Count == 0 && pictures.Count == 0)
                yield break;    
            OfficeArtDgContainer container = new OfficeArtDgContainer();
            MemoryStream memoryStream = new MemoryStream();
            using (BinaryWriter writer = new BinaryWriter(memoryStream))
            {
                OfficeArtWriteContext context = new OfficeArtWriteContext(writer, Context);
                foreach (var item in GetShapes(context))
                    container.AddGroupShape(item);
                container.Write(context);
                writer.Flush();
                foreach (var pair in context.CommentToObjectTable)
                {
                    _parentContext.AddCommentToObject(pair.Key, pair.Value);
                }
                BiffRecord previousRecord = null;
                foreach (var segment in context.GetSegments())
                {
                    var positions = segment.First;
                    if (!positions.IsEmpty)
                    {
                        if (positions.IsFirst || previousRecord is ObjRecord)
                            yield return new MsoDrawingRecord(positions.GetBytes(memoryStream));
                        else
                        {
                            yield return new ContinueRecord(){Buffer = positions.GetBytes(memoryStream)};
                        }
                    }
                    if (segment.Second != null)
                    {
                        yield return segment.Second;
                        TxORecord txORecord = segment.Second as TxORecord;
                        if (txORecord != null)
                        {
                            foreach (ContinueRecord continueRecord in txORecord.GetContinueRecords())
                                yield return continueRecord;
                        }
                    }
                    previousRecord = segment.Second;
                }
            }
            Debug.WriteLine("_parentContext.CommentsCount" + _parentContext.CommentsCount);
            Debug.WriteLine("cellWithComments.Count" + cellWithComments.Count);
            Contract.Assert((_parentContext.CommentsCount == cellWithComments.Count));
        }

        private  IEnumerable<BiffRecord> GetHLinks()
        {
            var cells = Enumerable.Where(this.Worksheet.NonVolatileCells, c => c.Hyperlink != null);
            foreach (var cell in cells)
            {
                yield return new HLinkRecord(cell);
            }
        }

        private  IEnumerable<BiffRecord> MergedCells()
        {
#if DEBUG
            Contract.Assert(this.Worksheet.IsValid);
#endif
            var cells = Enumerable.ToList(Enumerable.Where(this.Worksheet.GetCells(), c => c.HasSpan));
            if (cells.Count > 0)
                yield return new MergedCellsRecord(cells);
        }

        private  IEnumerable<BiffRecord> Background()
        {
            Contract.Requires(this.Worksheet != null);
            Contract.Requires(this.Worksheet.Background != null);
            return BkHimRecord.GetRecords(this.Worksheet.Background);
        }

        private  IEnumerable<BiffRecord> DataValidation_()
        {
            if (this.Worksheet.DataValidations.Count == 0)
                yield break;
            //\\yield break;
            yield return new DValRecord(this.Worksheet.DataValidations.Count);
            foreach (var dataValidation in this.Worksheet.DataValidations)
                yield return new DvRecord(dataValidation, Context);
        }

        private  IEnumerable<BiffRecord> ConditionalFormatting_()
        {
            ushort id = 1;
            foreach (var conditionalFormatting in this.Worksheet.ConditionalFormatting)
            {
                if (IsConditionalFormatting12(conditionalFormatting))
                {
                    yield return new CondFmt12Record(conditionalFormatting, id);
                    foreach (var rule in conditionalFormatting.Rules)
                    {
                        rule.PrepareBeforeWrite(conditionalFormatting, ConditionalFormattingRule.FormatType.Biff);
                        yield return new CF12Record(rule, Context, conditionalFormatting.ReferenceCell);
                        
                    }
                }
                else
                {
                    yield return new CondFmtRecord(conditionalFormatting, id);
                    foreach (var rule in conditionalFormatting.Rules)
                    {
                        rule.PrepareBeforeWrite(conditionalFormatting, ConditionalFormattingRule.FormatType.Biff);
                        yield return new CFRecord(rule, Context, conditionalFormatting.ReferenceCell);
                    }
                }
                id++;
            }
            id = 1;
            foreach (var conditionalFormatting in this.Worksheet.ConditionalFormatting)
            {
                foreach (var rule in Enumerable.OfType<StandardConditionalFormattingRule>(conditionalFormatting.Rules))
                {
                    yield return new CFExRecord(conditionalFormatting.Range, rule, id, Context);
                }
                id++;
            }
        }

        private bool IsConditionalFormatting12(ConditionalFormatting conditionalFormatting)
        {
            foreach (var rule in conditionalFormatting.Rules)
            {
                if (rule is ColorScaleFormattingRule)
                    return true;
                if (rule is DataBarFormattingRule)
                    return true;
                if (rule is IconSetFormattingRule)
                    return true;
            }
            return false;   

        }

        private  IEnumerable<BiffRecord> SortAndFilter()
        {
            // [Sort] [SORTDATA12] [FilterMode] [DropDownObjIds] [AUTOFILTER]
            ;
            if (this.Worksheet.AutoFilter != null)
                foreach (var record in AutoFilterInfoRecord.GetAutoFilterRecords(this.Worksheet.AutoFilter))
                    yield return record;
               //== yield return new AutoFilterInfoRecord(worksheet.AutoFilter);
        }

        private  IEnumerable<BiffRecord> CellTable(IEnumerable<RowGroup> rowGroups)
        {
            foreach (var rowGroup in rowGroups)
            {
                foreach (var row in rowGroup)
                {
                    yield return new RowRecord(row);
                }
                foreach (var row in rowGroup)
                {
                    foreach (Cell cell in Enumerable.Where(row.GetCells(), c => !c.IsVolatile))
                    {
                        yield return CellRecord.Create(cell, Context);
                        if (cell.Formula != null && cell.Value is string && ((string)cell.Value).Length > 0)
                            yield return new StringRecord((string)cell.Value);
                        SharedExpressionReference sharedExpressionReference = cell.Expression as SharedExpressionReference;
                        ;
                        if (sharedExpressionReference != null)
                        {
                            if (sharedExpressionReference.SharedExpression.Address.TopLeft == cell.Address)
                            {
                                yield return
                                    new SharedFormulaRecord(sharedExpressionReference.SharedExpression, Context, cell);
                            }
                        }
                    }
                }
                yield return new DbCellRecord();
            }
        }

        private  IEnumerable<RowGroup> GetRowGroups()
        {
            RowGroup currentGroup = null;
            foreach (var row in Enumerable.Where(this.Worksheet.Rows, r => Enumerable.Any(r.GetCells(), c => !c.IsVolatile) || r.IsCustom))
            {
                if (currentGroup == null)
                {
                    currentGroup = new RowGroup(row);
                    continue;
                }
                if (currentGroup.CanAccept(row))
                {
                    currentGroup.Add(row);
                }
                else
                {
                    yield return currentGroup;
                    currentGroup = new RowGroup(row);
                }
            }
            if (currentGroup != null)
                yield return currentGroup;
        }

        internal class RowGroup : IEnumerable<Row>
        {
            List<Row> rows = new List<Row>();
            public RowGroup(Row row)
            {
                Contract.Requires(row != null);
                rows.Add(row);
            }
            public bool CanAccept(Row row)
            {
                Contract.Requires(row != null);
                if (rows.Count == 32)
                    return false;
                //    int diff = row.Index - LastRow.Index;
                return true; //\\ diff == 1;

            }

            Row LastRow
            {
                get { return rows[rows.Count - 1]; }
            }

            public void Add(Row row)
            {
                Contract.Assert(this.rows.Count < 32);
                rows.Add(row);
            }

            public IEnumerator<Row> GetEnumerator()
            {
                return this.rows.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }

        private  IEnumerable<BiffRecord> PAGESETUP()
        {
            //HeaderFooterHCenterVCenter [LeftMargin] [RightMargin] [TopMargin] [BottomMargin] [Pls *Continue] Setup
            yield return new HeaderRecord();
            yield return new FooterRecord();
            yield return new HCenterRecord();
            yield return new VCenterRecord();
            yield return new LeftMarginRecord();
            yield return new RightMarginRecord();
            yield return new TopMarginRecord();
            yield return new BottomMarginRecord();
            //\\
            yield return new SetupRecord();
            yield return new HeaderFooterRecord();
        }

        private  IEnumerable<BiffRecord> GlobalsRecords()
        {
            yield return new CalcModeRecord(this.Worksheet);
            yield return new CalcCountRecord();
            yield return new CalcRefModeRecord(this.Worksheet);
            yield return new CalcIterRecord();
            yield return new CalcDeltaRecord();
            yield return new CalcSaveRecalcRecord();
            yield return new PrintRowColRecord(this.Worksheet);
            yield return new PrintGridRecord(this.Worksheet);
            yield return new GridSetRecord();
            yield return new GutsRecord(this.Worksheet);
            yield return new DefaultRowHeightRecord();
            yield return new WsBoolRecord();
            /// [Sync] [LPr] [HorizontalPageBreaks] [VerticalPageBreaks]
        }

        private  IEnumerable<BiffRecord> Columns()
        {
            yield return new DefColWidthRecord();
            foreach (var columnSegment in this.Worksheet.Columns.GetColumnSegments())
            {
                if (columnSegment.First.Index > ColumnInfoRecord.MaxColumnIndex)
                    continue;
                var last = columnSegment.Second;
                if (last.Index > ColumnInfoRecord.MaxColumnIndex)
                    last = last.Worksheet.Columns[ColumnInfoRecord.MaxColumnIndex];
                yield return new ColumnInfoRecord(columnSegment.First, last, Context);
            }
        }
    }
}